package com.huzz.filter;

import com.huzz.domain.LoginModel;
import com.huzz.domain.Payload;
import com.huzz.entity.SysUser;
import com.huzz.properties.RsaKeyProperties;
import com.huzz.properties.TokenProperties;
import com.huzz.util.*;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 认证过滤器
 *
 * @author Huzz
 * @create 2022-08-18 12:15
 */
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    /**
     * 认证管理器
     */
    private AuthenticationManager authenticationManager;

    /**
     * Token加密私钥获取
     */
    private RsaKeyProperties rsaKeyProperties;

    /**
     * Token生成配置
     */
    private TokenProperties tokenProperties;

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager, RsaKeyProperties rsaKeyProperties, TokenProperties tokenProperties) {
        this.authenticationManager = authenticationManager;
        this.rsaKeyProperties = rsaKeyProperties;
        this.tokenProperties = tokenProperties;
    }

    /**
     * 登录认证
     *
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        LoginModel loginUser = RequestUtils.read(request, LoginModel.class);
        String username = loginUser.getUsername();
        username = username != null ? username : "";
        String password = loginUser.getPassword();
        password = password != null ? password : "";
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        return authenticationManager.authenticate(authRequest);
    }

    /**
     * 认证成功执行
     *
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        String user_key = IdUtils.fastUUID();

        SysUser userInfo = new SysUser();
        userInfo.setUsername(authResult.getName());
        Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
        userInfo.setAuthorities(authorities);
        // 生成token
        String jwtToken = generateJwtToken(user_key, userInfo);
        String token = "Bearer " + jwtToken;
        response.addHeader("Authorization", token);

        ResponseUtils.write(response, HttpServletResponse.SC_OK, "登录成功", token);
    }

    /**
     * 生成Token
     *
     * @param user_key
     * @param userInfo
     * @return
     */
    private String generateJwtToken(String user_key, SysUser userInfo) {
        String jwtToken;
        if (tokenProperties.isOnCacheToken()) {
            jwtToken = JwtUtil.generateAccessToken(user_key, userInfo, rsaKeyProperties.getPrivateKey());
        } else {
            // 不使用缓存刷新token过期时间, 但需要存入版本号用于删除
            jwtToken = JwtUtil.generateTokenExpireInMinutes(user_key, userInfo, rsaKeyProperties.getPrivateKey(), tokenProperties.getExpire());
        }
        // 使用缓存时刷新token过期时间
        setRefreshToken(user_key, userInfo);

        return jwtToken;
    }

    /**
     * 缓存会话信息
     *
     * @param user_key
     * @param userInfo
     */
    private void setRefreshToken(String user_key, SysUser userInfo) {
        RedisTemplate redisTemplate = SpringUtils.getBean("redisTemplate");
        try {

            Payload<SysUser> body = new Payload<>();
            body.setUserInfo(userInfo);
            body.setExpiration(DateTime.now().plusMinutes(tokenProperties.getExpire()).toDate());

            if (tokenProperties.isOnCacheToken()) {
                redisTemplate.opsForValue().set(user_key, body, tokenProperties.getExpire(), TimeUnit.MINUTES);
            } else {
                redisTemplate.opsForValue().set(user_key, body, 10, TimeUnit.DAYS);
            }

            logger.info("缓存用户会话信息");
        } catch (Exception e) {
            logger.error("缓存会话信息失败");
        }
    }

    /**
     * 登录认证失败执行
     *
     * @param request
     * @param response
     * @param failed
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        //清理上下文
        SecurityContextHolder.clearContext();
        System.out.println(failed);
        //判断异常类
        if (failed instanceof InternalAuthenticationServiceException) {
            ResponseUtils.write(response, HttpServletResponse.SC_FORBIDDEN, "认证服务不正常！");
        } else if (failed instanceof UsernameNotFoundException) {
            ResponseUtils.write(response, HttpServletResponse.SC_FORBIDDEN, "用户账户不存在！");
        } else if (failed instanceof BadCredentialsException) {
            ResponseUtils.write(response, HttpServletResponse.SC_FORBIDDEN, "用户密码是错的！");
        } else if (failed instanceof AccountExpiredException) {
            ResponseUtils.write(response, HttpServletResponse.SC_FORBIDDEN, "用户账户已过期！");
        } else if (failed instanceof LockedException) {
            ResponseUtils.write(response, HttpServletResponse.SC_FORBIDDEN, "用户账户已被锁！");
        } else if (failed instanceof CredentialsExpiredException) {
            ResponseUtils.write(response, HttpServletResponse.SC_FORBIDDEN, "用户密码已失效！");
        } else if (failed instanceof DisabledException) {
            ResponseUtils.write(response, HttpServletResponse.SC_FORBIDDEN, "用户账户已被锁！");
        }
    }
}
